﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Library.Linq.Search
{
    public static class Searchable
    {
        public static IEnumerable<TResult> AsEnumerable<TSearch, TResult>(this ISearchable<TSearch, TResult> source)
        {
            IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
            if (enumerable != null)
                return enumerable;

            throw new NotImplementedException();
        }

        public static IOrderedSearchable<TSearch, TResult> OrderBy<TSearch, TResult, TKey>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector) }));
        }
        public static IOrderedSearchable<TSearch, TResult> OrderBy<TSearch, TResult, TKey>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector, IComparer<TKey> comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Constant(comparer, typeof(IComparer<TKey>)) }));
        }
        public static IOrderedSearchable<TSearch, TResult> OrderByDescending<TSearch, TResult, TKey>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector) }));
        }
        public static IOrderedSearchable<TSearch, TResult> OrderByDescending<TSearch, TResult, TKey>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector, IComparer<TKey> comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Constant(comparer, typeof(IComparer<TKey>)) }));
        }

        public static ISearchable<TSearch, TOutput> Select<TSearch, TResult, TOutput>(this ISearchable<TSearch, TResult> source, Expression<Func<TResult, TOutput>> selector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (selector == null)
            {
                throw new ArgumentNullException("selector");
            }
            return source.Provider.CreateSearch<TSearch, TOutput>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TOutput) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
        }

        public static ISearchable<TSearch, TOutput> SelectMany<TSearch, TResult, TOutput>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, IEnumerable<TOutput>>> selector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (selector == null)
            {
                throw new ArgumentNullException("selector");
            }
            return source.Provider.CreateSearch<TSearch, TOutput>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TOutput) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
        }
        public static ISearchable<TSearch, TOutput> SelectMany<TSearch, TResult, TOutput>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, int, IEnumerable<TOutput>>> selector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (selector == null)
            {
                throw new ArgumentNullException("selector");
            }
            return source.Provider.CreateSearch<TSearch, TOutput>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TOutput) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
        }
        public static ISearchable<TSearch, TOutput> SelectMany<TSearch, TResult, TCollection, TOutput>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, IEnumerable<TCollection>>> collectionSelector, Expression<Func<TSearch, TCollection, TOutput>> resultSelector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (collectionSelector == null)
            {
                throw new ArgumentNullException("collectionSelector");
            }
            if (resultSelector == null)
            {
                throw new ArgumentNullException("resultSelector");
            }
            return source.Provider.CreateSearch<TSearch, TOutput>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TCollection), typeof(TOutput) }), new Expression[] { source.Expression, Expression.Quote(collectionSelector), Expression.Quote(resultSelector) }));
        }
        public static ISearchable<TSearch, TOutput> SelectMany<TSearch, TResult, TCollection, TOutput>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, int, IEnumerable<TCollection>>> collectionSelector, Expression<Func<TSearch, TCollection, TOutput>> resultSelector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (collectionSelector == null)
            {
                throw new ArgumentNullException("collectionSelector");
            }
            if (resultSelector == null)
            {
                throw new ArgumentNullException("resultSelector");
            }
            return source.Provider.CreateSearch<TSearch, TOutput>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TCollection), typeof(TOutput) }), new Expression[] { source.Expression, Expression.Quote(collectionSelector), Expression.Quote(resultSelector) }));
        }

        public static IOrderedSearchable<TSearch, TResult> ThenBy<TSearch, TResult, TKey>(this IOrderedSearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector) }));
        }
        public static IOrderedSearchable<TSearch, TResult> ThenBy<TSearch, TResult, TKey>(this IOrderedSearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector, IComparer<TKey> comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Constant(comparer, typeof(IComparer<TKey>)) }));
        }
        public static IOrderedSearchable<TSearch, TResult> ThenByDescending<TSearch, TResult, TKey>(this IOrderedSearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector) }));
        }
        public static IOrderedSearchable<TSearch, TResult> ThenByDescending<TSearch, TResult, TKey>(this IOrderedSearchable<TSearch, TResult> source, Expression<Func<TSearch, TKey>> keySelector, IComparer<TKey> comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            return (IOrderedSearchable<TSearch, TResult>)source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Constant(comparer, typeof(IComparer<TKey>)) }));
        }

        public static ISearchable<TSearch, TResult> Where<TSearch, TResult>(this ISearchable<TSearch, TResult> source, Expression<Func<TSearch, bool>> predicate)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (predicate == null)
            {
                throw new ArgumentNullException("predicate");
            }
            return source.Provider.CreateSearch<TSearch, TResult>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSearch), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(predicate) }));
        }
    }
}